package com.moss.cloud.common.core.utils.file;

import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;

/**
 * 智能图片加载工具
 * 支持大图片分块加载，避免内存溢出
 * 对小图片采用普通加载方式
 *
 * @author 瑾年
 * @date 2025年1月8日
 */
@Slf4j
public class SmartImageLoader {

    // 默认阈值：2MB
    private static final long DEFAULT_SIZE_THRESHOLD = 2 * 1024 * 1024L;
    // 默认块大小：512x512
    private static final int DEFAULT_BLOCK_SIZE = 512;
    // 默认最大尺寸：4096px
    private static final int DEFAULT_MAX_DIMENSION = 4096;

    private final String filePath;
    private long sizeThreshold = DEFAULT_SIZE_THRESHOLD;
    private int blockSize = DEFAULT_BLOCK_SIZE;
    private int maxDimension = DEFAULT_MAX_DIMENSION;
    private Executor executor;
    private ImageLoadCallback callback;

    /**
     * 私有构造函数
     *
     * @param filePath 图片文件路径
     */
    private SmartImageLoader(String filePath) {
        this.filePath = filePath;
    }

    /**
     * 创建SmartImageLoader实例
     *
     * @param filePath 图片文件路径
     * @return SmartImageLoader实例
     */
    public static SmartImageLoader from(String filePath) {
        return new SmartImageLoader(filePath);
    }

    /**
     * 设置文件大小阈值
     *
     * @param threshold 阈值（字节）
     * @return this，支持链式调用
     */
    public SmartImageLoader withSizeThreshold(long threshold) {
        this.sizeThreshold = threshold;
        return this;
    }

    /**
     * 设置分块大小
     *
     * @param blockSize 块大小（像素）
     * @return this，支持链式调用
     */
    public SmartImageLoader withBlockSize(int blockSize) {
        this.blockSize = blockSize;
        return this;
    }

    /**
     * 设置最大尺寸
     *
     * @param maxDimension 最大尺寸（像素）
     * @return this，支持链式调用
     */
    public SmartImageLoader withMaxDimension(int maxDimension) {
        this.maxDimension = maxDimension;
        return this;
    }

    /**
     * 设置执行器
     *
     * @param executor 执行器实例
     * @return this，支持链式调用
     */
    public SmartImageLoader withExecutor(Executor executor) {
        this.executor = executor;
        return this;
    }

    /**
     * 设置加载回调
     *
     * @param callback 回调接口实现
     * @return this，支持链式调用
     */
    public SmartImageLoader withCallback(ImageLoadCallback callback) {
        this.callback = callback;
        return this;
    }

    /**
     * 加载图片
     * 根据图片大小自动选择加载策略
     *
     * @return BufferedImage 加载后的图片
     * @throws IOException 如果加载失败
     */
    public BufferedImage load() throws IOException {
        File imageFile = new File(filePath);
        if (!imageFile.exists()) {
            throw new FileNotFoundException("图片文件不存在: " + filePath);
        }

        long fileSize = imageFile.length();
        log.info("开始加载图片: {}, 大小: {} bytes", filePath, fileSize);

        try {
            BufferedImage result;
            if (fileSize <= sizeThreshold) {
                log.info("使用直接加载方式");
                result = loadSmallImage(imageFile);
            } else {
                log.info("使用分块加载方式");
                result = loadLargeImage(imageFile);
            }

            if (callback != null) {
                callback.onComplete(result);
            }
            return result;

        } catch (Exception e) {
            if (callback != null) {
                callback.onError(e);
            }
            throw e;
        }
    }

    /**
     * 小图片加载方法
     *
     * @param imageFile 图片文件
     * @return BufferedImage 加载后的图片
     * @throws IOException 如果加载失败
     */
    private BufferedImage loadSmallImage(File imageFile) throws IOException {
        try {
            return ImageIO.read(imageFile);
        } catch (OutOfMemoryError e) {
            log.warn("小图片加载发生OOM，切换到分块加载模式");
            return loadLargeImage(imageFile);
        }
    }

    /**
     * 大图片分块加载方法
     *
     * @param imageFile 图片文件
     * @return BufferedImage 加载后的图片
     * @throws IOException 如果加载失败
     */
    private BufferedImage loadLargeImage(File imageFile) throws IOException {
        long startTime = System.currentTimeMillis();
        File tempFile = null;

        try (FileInputStream fis = new FileInputStream(imageFile);
             ImageInputStream iis = ImageIO.createImageInputStream(fis)) {

            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (!readers.hasNext()) {
                throw new IOException("未找到合适的图片解码器");
            }

            ImageReader reader = readers.next();
            try {
                reader.setInput(iis, true, true);

                // 获取原始尺寸
                int originalWidth = reader.getWidth(0);
                int originalHeight = reader.getHeight(0);
                log.info("原始图片尺寸: {}x{}", originalWidth, originalHeight);

                // 计算缩放比例
                double scale = calculateScaleIfNeeded(originalWidth, originalHeight);
                int targetWidth = (int) (originalWidth * scale);
                int targetHeight = (int) (originalHeight * scale);
                log.info("目标图片尺寸: {}x{}, 缩放比例: {}", targetWidth, targetHeight, scale);

                // 创建临时文件
                tempFile = File.createTempFile("image_processing_", ".tmp");
                tempFile.deleteOnExit();

                // 分块处理并写入临时文件
                processImageInBlocks(reader, tempFile, scale, targetWidth, targetHeight);

                // 从临时文件创建最终图片
                return createFinalImage(tempFile, targetWidth, targetHeight);

            } finally {
                reader.dispose();
            }
        } finally {
            // 清理临时文件
            if (tempFile != null && tempFile.exists()) {
                tempFile.delete();
            }
        }
    }

    /**
     * 分块处理图片
     */
    private void processImageInBlocks(ImageReader reader, File tempFile,
                                      double scale, int targetWidth, int targetHeight)
            throws IOException {

        try (RandomAccessFile raf = new RandomAccessFile(tempFile, "rw")) {
            // 写入图片头信息
            raf.writeInt(targetWidth);
            raf.writeInt(targetHeight);

            ImageReadParam param = reader.getDefaultReadParam();
            if (scale != 1.0) {
                param.setSourceSubsampling((int) (1 / scale), (int) (1 / scale), 0, 0);
            }

            int numBlocksX = (targetWidth + blockSize - 1) / blockSize;
            int numBlocksY = (targetHeight + blockSize - 1) / blockSize;
            int totalBlocks = numBlocksX * numBlocksY;
            int processedBlocks = 0;

            // 逐块处理
            for (int blockY = 0; blockY < targetHeight; blockY += blockSize) {
                for (int blockX = 0; blockX < targetWidth; blockX += blockSize) {
                    int currentBlockWidth = Math.min(blockSize, targetWidth - blockX);
                    int currentBlockHeight = Math.min(blockSize, targetHeight - blockY);

                    // 处理当前块
                    Rectangle sourceRegion = new Rectangle(
                            (int) (blockX / scale),
                            (int) (blockY / scale),
                            (int) Math.ceil(currentBlockWidth / scale),
                            (int) Math.ceil(currentBlockHeight / scale)
                    );
                    param.setSourceRegion(sourceRegion);

                    BufferedImage block = reader.read(0, param);

                    // 如果需要缩放，调整块大小
                    if (scale != 1.0) {
                        BufferedImage scaledBlock = new BufferedImage(
                                currentBlockWidth, currentBlockHeight, BufferedImage.TYPE_INT_RGB);
                        Graphics2D g2d = scaledBlock.createGraphics();
                        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                        g2d.drawImage(block, 0, 0, currentBlockWidth, currentBlockHeight, null);
                        g2d.dispose();
                        block = scaledBlock;
                    }

                    // 写入块数据
                    writeBlockToFile(raf, block, blockX, blockY, currentBlockWidth, currentBlockHeight);

                    block.flush();
                    processedBlocks++;
                    updateProgress(processedBlocks, totalBlocks);
                }
            }
        }
    }

    /**
     * 处理单个图片块
     */
    private void processBlock(ImageReader reader, RandomAccessFile raf,
                              ImageReadParam param, int blockX, int blockY,
                              int width, int height, double scale) throws IOException {
        try {
            // 计算源图像区域
            Rectangle sourceRegion = new Rectangle(
                    (int) (blockX / scale),
                    (int) (blockY / scale),
                    (int) Math.ceil(width / scale),
                    (int) Math.ceil(height / scale)
            );
            param.setSourceRegion(sourceRegion);

            // 读取块
            BufferedImage block = reader.read(0, param);

            // 如果需要缩放，创建正确大小的块
            if (scale != 1.0) {
                BufferedImage scaledBlock = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = scaledBlock.createGraphics();
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.drawImage(block, 0, 0, width, height, null);
                g2d.dispose();
                block = scaledBlock;
            }

            // 写入块数据
            writeBlockToFile(raf, block, blockX, blockY, width, height);

            // 清理资源
            block.flush();
        } catch (OutOfMemoryError e) {
            log.error("处理块时发生OOM: ({},{})", blockX, blockY, e);
            throw new IOException("内存不足，无法处理图片", e);
        }
    }

    /**
     * 更新处理进度
     */
    private void updateProgress(int processed, int total) {
        if (callback != null) {
            int progress = (processed * 100) / total;
            callback.onProgress(progress);
        }
    }

    /**
     * 计算缩放比例
     */
    private double calculateScaleIfNeeded(int width, int height) {
        if (width > maxDimension || height > maxDimension) {
            double widthScale = (double) maxDimension / width;
            double heightScale = (double) maxDimension / height;
            return Math.min(widthScale, heightScale);
        }
        return 1.0;
    }

    /**
     * 异步加载图片
     *
     * @return CompletableFuture<BufferedImage> 异步加载的图片
     */
    public CompletableFuture<BufferedImage> loadAsync() {
        if (executor == null) {
            throw new IllegalStateException("异步加载需要设置executor");
        }

        return CompletableFuture.supplyAsync(() -> {
            try {
                return load();
            } catch (IOException e) {
                throw new CompletionException(e);
            }
        }, executor);
    }
    /**
     * 将图片块数据写入临时文件
     *
     * @param raf RandomAccessFile实例
     * @param block 图片块
     * @param x 块的x坐标
     * @param y 块的y坐标
     * @param width 块宽度
     * @param height 块高度
     * @throws IOException 如果写入失败
     */
    private void writeBlockToFile(RandomAccessFile raf, BufferedImage block,
                                  int x, int y, int width, int height) throws IOException {
        // 获取总图片的宽度（从文件头部读取）
        raf.seek(0);
        int totalWidth = raf.readInt();

        // 每个像素3字节(RGB)，使用传入的 width 参数
        byte[] rowData = new byte[width * 3];

        // 逐行写入数据
        for (int py = 0; py < height; py++) {
            // 计算在总图片中的实际行位置
            long rowOffset = ((long)(y + py) * totalWidth * 3) + (x * 3);
            // 定位到数据区域（跳过8字节头信息）
            long position = 8L + rowOffset;
            raf.seek(position);

            // 填充行数据
            int dataIndex = 0;
            for (int px = 0; px < width; px++) {
                int rgb = block.getRGB(px, py);
                rowData[dataIndex++] = (byte)((rgb >> 16) & 0xFF); // R
                rowData[dataIndex++] = (byte)((rgb >> 8) & 0xFF);  // G
                rowData[dataIndex++] = (byte)(rgb & 0xFF);         // B
            }

            // 写入这一行的数据
            raf.write(rowData);
        }
    }

    /**
     * 从临时文件创建最终图片
     *
     * @param tempFile 临时文件
     * @param width 目标图片宽度
     * @param height 目标图片高度
     * @return BufferedImage 最终图片
     * @throws IOException 如果读取失败
     */
    private BufferedImage createFinalImage(File tempFile, int width, int height) throws IOException {
        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        try (RandomAccessFile raf = new RandomAccessFile(tempFile, "r")) {
            // 读取文件头信息
            raf.seek(0);
            int storedWidth = raf.readInt();
            int storedHeight = raf.readInt();

            if (storedWidth != width || storedHeight != height) {
                throw new IOException("存储的图片尺寸与预期不符");
            }

            // 分块读取数据
            byte[] blockBuffer = new byte[blockSize * 3]; // 每次读取一行的数据

            // 逐行读取
            for (int y = 0; y < height; y++) {
                // 计算当前行的文件偏移量
                long rowOffset = ((long)y * width * 3);
                long position = 8L + rowOffset;
                raf.seek(position);

                // 分段读取这一行
                for (int x = 0; x < width; x += blockSize) {
                    int currentWidth = Math.min(blockSize, width - x);
                    byte[] rowData = new byte[currentWidth * 3];
                    raf.read(rowData);

                    // 设置像素
                    for (int px = 0; px < currentWidth; px++) {
                        int baseIndex = px * 3;
                        int r = rowData[baseIndex] & 0xFF;
                        int g = rowData[baseIndex + 1] & 0xFF;
                        int b = rowData[baseIndex + 2] & 0xFF;
                        int rgb = (r << 16) | (g << 8) | b;
                        result.setRGB(x + px, y, rgb);
                    }
                }
            }
        }
        return result;
    }

    /**
     * 设置图片像素数据
     *
     * @param image 目标图片
     * @param buffer 像素数据缓冲区
     * @param startX 起始X坐标
     * @param startY 起始Y坐标
     * @param width 宽度
     * @param height 高度
     */
    private void setPixels(BufferedImage image, byte[] buffer,
                           int startX, int startY, int width, int height) {
        int index = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int r = buffer[index++] & 0xFF;
                int g = buffer[index++] & 0xFF;
                int b = buffer[index++] & 0xFF;
                int rgb = (r << 16) | (g << 8) | b;
                image.setRGB(startX + x, startY + y, rgb);
            }
        }
    }

    /**
     * 简单的回调实现类
     */
    public static class SimpleImageLoadCallback implements ImageLoadCallback {
        @Override
        public void onProgress(int progress) {
            log.info("加载进度: {}%", progress);
        }

        @Override
        public void onComplete(BufferedImage image) {
            log.info("图片加载完成: {}x{}", image.getWidth(), image.getHeight());
        }

        @Override
        public void onError(Exception e) {
            log.error("图片加载失败", e);
        }
    }

    /**
     * 构建异常类
     */
    public static class ImageLoadException extends IOException {
        public ImageLoadException(String message) {
            super(message);
        }

        public ImageLoadException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
